package model.filemanager;

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;

import model.MediaProperty;


import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.datastore.Transaction;
/**
 * File manager class is used to interact with the datastore and manage Mediafiles
 */
public class FileManager {
	
	private DatastoreService datastore;
	private String user;
	private MemcacheService deleteCache;
	
	// Construct a FileManager instance for the given user
	public FileManager(String user){
		datastore = DatastoreServiceFactory.getDatastoreService();
		this.user = user;
		
        deleteCache = MemcacheServiceFactory.getMemcacheService();
            
	}
	
	/*
	 *  Search for files with a list of parameters:
	 *  @param type the type of the files (image, audio, video)
	 *  @param searchParam the parameter which this search will apply to
	 *  	(name, caption, description, etc..)
	 *  @param searchQuery the query which is used to search
	 *  @param sortParam the parameter used for sorting
	 *  @param descending whether the list will be sorted descending or not (default is ascending)
	 *  @param offSet the offset of the list (by default is 0-9)
	 *  @param count the maximum numbers of item can be included in the search
	 *   
	 */
	public List<MediaFile> SearchForFiles(String type, String searchParam, 
										String searchQuery, String sortParam, long listKey,
										boolean descending, int offSet, int count, Integer[] totalCount) 
			throws EntityNotFoundException
	{
		List<Entity> entities = this.GetEntities(type, sortParam, descending);
		
		List<Entity> newEntities = new ArrayList<Entity>();
		String[] newKeys = (String[])deleteCache.get("new");
		List<String> actualNewKeys = new ArrayList<String>();
		if (newKeys != null)
		{
			for(String key : newKeys)
			{
				Entity e = (Entity)deleteCache.get(key);
				if (e != null)
				{
					newEntities.add(e);
				}
			}
		}
		Iterator<Entity> itr1 = entities.iterator();
		Iterator<Entity> itr2 = null;
		Entity e1 = null;
		Entity e2 = null;
		while(itr1.hasNext())
		{
			e1 = itr1.next();
			itr2 = newEntities.iterator();
			while(itr2.hasNext())
			{
				e2 = itr2.next();
				if (((String)e2.getProperty("blobkey")).contentEquals(((String)e1.getProperty("blobkey"))))
				{
					itr2.remove();
				}
			}		
		}
		for (Entity e : newEntities) {
			actualNewKeys.add((String) e.getProperty("blobkey"));
			if (type == null || ((String)e.getProperty("type")).contentEquals(type))
				entities.add(0, e);				
		}
		deleteCache.put("new", actualNewKeys.toArray(new String[0]));
		
		
		Entity ListEnt = MediaList.getListEntity(listKey);
		if(!(listKey == -1 || ListEnt == null)) 
			for (int i=0; i<entities.size();i++) {
				Entity ent = entities.get(i);
				if(ent!= null) 
					if(MediaList.getMediaEntity(ListEnt.getKey().getId(),ent.getKey().getName())==null){
						entities.remove(i);
						i--;
				}
		}
		
		List<MediaFile> mediaFiles = this.ApplySearch(entities, searchParam, searchQuery);
		
		totalCount[0] = mediaFiles.size();
		
		if(mediaFiles.size() == 0)
			return mediaFiles;

		return mediaFiles.subList(Math.min(mediaFiles.size() - 1, offSet), 
								  Math.min(mediaFiles.size(), (offSet + count)));
	}
	
	/*
	 * Given a MediaFile object, update the content of its copy in datastore
	 */
	public void UpdateFile(MediaFile mediaFile)
	{
		Entity entity = mediaFile.getEntity();
		deleteCache.put(mediaFile.getKey(), entity, Expiration.byDeltaSeconds(10));
		if (FileIsDeleted(mediaFile))
			return;	
		datastore.put(entity);
	}
	
	// Check if a file has been deleted or not
	public boolean FileIsDeleted(MediaFile mediaFile)
	{
		Entity obj = (Entity)deleteCache.get(mediaFile.getKey());
		if (obj == null)
			return false;
		Boolean result = (Boolean)(obj.getProperty("deleted"));
		return result;

	}
	
	// Get a file from datastore, given a Blobkey
	public MediaFile GetFile(String blobkey) throws EntityNotFoundException {
		Entity mediaEntity = datastore.get(KeyFactory.createKey("MediaFile", blobkey));
		return (new MediaFile(mediaEntity));
	}

	// Save a mediafile into datastore, given its key, name, type and size
	public void SaveFile(BlobKey key, String name, String type, long size)
		throws RuntimeException
	{
		String blobKey = key.getKeyString();
        Entity mediaFile = new Entity("MediaFile", blobKey);
        mediaFile.setProperty("name", name);
        mediaFile.setProperty("user", user);
        mediaFile.setProperty("blobkey", blobKey);
        mediaFile.setProperty("type", type);
        mediaFile.setProperty("size", size);
        mediaFile.setProperty("artist", "");
        mediaFile.setProperty("album", "");
        mediaFile.setProperty("actor", "");
        mediaFile.setProperty("desc", "");
        mediaFile.setProperty("title", name);
        mediaFile.setProperty("genre", "");
        mediaFile.setProperty("deleted", false);
        
        try {
        	datastore.get(mediaFile.getKey());
        	throw new RuntimeException("File with key already exists");
        } catch(EntityNotFoundException e) {
        }
        String[] keys = null;
        String[] newKeys = null;
        Object newValue = deleteCache.get("new");
        if (newValue == null)
        {
        	newKeys = new String[1];
        	newKeys[0] = blobKey;
        }
        else
        {
        	keys = (String[])newValue;
        	newKeys = new String[keys.length+1];
        	for(int i = 0; i<keys.length; i++)
        		newKeys[i] = keys[i];
        	newKeys[keys.length] = blobKey;
        }
        deleteCache.put("new", newKeys, Expiration.byDeltaSeconds(10));
        deleteCache.put(blobKey, mediaFile, Expiration.byDeltaSeconds(10));
        datastore.put(mediaFile);
	}	
	
	// Delete a file from datastore, given its blobkey
	public void DeleteFile(String blobKey)
	{

		BlobstoreService blobStoreService = BlobstoreServiceFactory.getBlobstoreService();
		Key bkey = KeyFactory.createKey("MediaFile", blobKey);
		MediaFile file = null;
		try {
			 file = GetFile(blobKey);
		} catch (EntityNotFoundException e) {
			return;
		}
		file.setProperty("deleted", true);
		UpdateFile(file);
		datastore.delete(bkey);
		BlobKey key = new BlobKey(blobKey);
		blobStoreService.delete(key);
	}
	
	// Get all entities with a specific type (the result can be sorted)
	public List<Entity> GetEntities(String type, String sort, Boolean descending) {
		
		Query query = new Query("MediaFile");
		query.addFilter("user", Query.FilterOperator.EQUAL, user);
		
		if (type != null)
			query.addFilter("type", Query.FilterOperator.EQUAL, type);
		
		if (sort != null)
			query.addSort(sort, descending ? SortDirection.DESCENDING : SortDirection.ASCENDING);

		PreparedQuery pq = datastore.prepare(query);
			
		return pq.asList(FetchOptions.Builder.withDefaults());
	}
	
	/**
	 * 
	 * @param entities
	 * @param key
	 * @param query
	 * @return
	 * @throws EntityNotFoundException
	 */
	public List<MediaFile> ApplySearch(List<Entity> entities, String key, String query) 
			throws EntityNotFoundException {

		List<MediaFile> result  = new ArrayList<MediaFile>();;
		
		for (Entity entity : entities)
		{
			MediaFile file = null;
			
			Entity updatedE = (Entity)deleteCache.get((String)entity.getProperty("blobkey"));
			if (updatedE != null) {
				file = new MediaFile(updatedE);
			} else {
				file = new MediaFile(entity);
			}
			
			if (FileIsDeleted(file)) {
				continue;
			}
			
			if (key != null && query != null)	{
				String property = (String)file.getProperty(key);
				
				if (property == null) {
					continue;
				}
				
				String lowerSearchQuery = query.toLowerCase();
				String lowerProperty = property.toLowerCase();

				if (lowerProperty.contains(lowerSearchQuery.subSequence(0, lowerSearchQuery.length())) == false) {
					continue;
				}   
			}
			
			result.add(file);
		}
		
		return result;
	}
	
}
